#pragma once

#include <map>
#include <vector>
#include "Complex.h"

class SortedAmplitudes;


/*
Implementation of FFT and inverse FFT
*/
class FFT
{
public:
	FFT(void);
	~FFT(void);

	/*
	Transforms short sample array into complexOut and then performs FFT inline in complexOut, before returning the FFT'd complex array.
	*/
	Complex* DoFFT(short* sample, unsigned int length);

	/*
	Performs inverse FFT on complexOut.
	Results are stored in shortOut.
	*/
	void InvFFT();

	/*
	Performs inverse FFT on the complex array, converting the result into short and storing them in the destination array.
	*/
	void InvFFT(Complex* complex, short* destination, unsigned int length);

	/*
	Normalizes an FFT'd array, necessary to prevent clipping after inverse FFT.
	*/
	void Normalize(Complex* complex, unsigned int length);

	/*
	Return arrays used by FFT
	*/
	unsigned int GetArrayLength();
	//double* GetRealIn();
	//Complex* GetComplexIn();
	Complex* GetComplexOut();
	//double* GetRealOut();
	short* GetShortOut();

	//static void ShiftComplexUp(Complex* complex, unsigned int length, unsigned int shiftSize);
	//static void ShiftComplexDown(Complex* complex, unsigned int length, unsigned int shiftSize);
private:
	/*
	Create necessary arrays (cos, sin, negative sin and bit reversions)
	*/
	void Initialize(unsigned int length);

	/*
	Real implementation of FFT, used both by 'normal' FFT with positive sinus array, and with inverse FFT using negative sinus array
	*/
	void FFTImpl(Complex* complex, unsigned int length, std::vector<double>& sinAr);

	std::vector<double> realIn;
	std::vector<Complex> complexIn;
	std::vector<Complex> complexOut;
	std::vector<double> realOut;
	std::vector<short> shortOut;	

	std::vector<double> cosAr;
	std::vector<double> sinAr;
	std::vector<double> sinArNeg;
	std::vector<int> bitRev;
	int nu;
};

/*
Interface defining classes that can do processing on Complex arrays of FFT'd content.
*/
class FFTProcessor
{
public:
	FFTProcessor(){}
	virtual ~FFTProcessor(){}

	/*
	Returns a pointer to a buffer owned by itself (see examples of implementing classes).
	This buffer shall contain a copy of the incoming Complex array, modified in whatever way is seen fit
	*/
	virtual Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes) = 0;
};

/*
FFTConvoluationProcessor stores a FFT'd sample, and mixes it with the incoming Complex array
*/
class FFTConvolutionProcessor : public FFTProcessor
{
public:
	FFTConvolutionProcessor(Complex* complex, unsigned int length);
	~FFTConvolutionProcessor();

	void SetComplex(Complex* complex, unsigned int length);
	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);

private:
	std::vector<Complex> myBuf;
};

/*
FFTRandomSwapProcessor randomly swaps fft buckets, creating a kind of ringing, alien sound
*/
class FFTRandomSwapProcessor : public FFTProcessor
{
public:
	FFTRandomSwapProcessor();
	~FFTRandomSwapProcessor();

	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);

private:
	std::vector<Complex> myBuf;
	std::map<int, int> swapMap;
};

/*
FFTLowPassProcessor nulls every entry whose index in the complex arrays is above the specified threshold
*/
class FFTLowPassProcessor : public FFTProcessor
{
public:
	FFTLowPassProcessor(unsigned int threshold);
	~FFTLowPassProcessor();

	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);

private:
	std::vector<Complex> myBuf;
	unsigned int threshold;

};

/*
FFTLowPassProcessor nulls every entry whose index in the complex arrays is below the specified threshold
*/
class FFTHiPassProcessor : public FFTProcessor
{
public:
	FFTHiPassProcessor(unsigned int threshold);
	~FFTHiPassProcessor();

	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);

private:
	std::vector<Complex> myBuf;
	unsigned int threshold;

};

/*
FFTBandPassProcessor nulls every entry whose index does not fall within the specified startIdx and endIdx
*/
class FFTBandPassProcessor : public FFTProcessor
{
public:
	FFTBandPassProcessor(unsigned int startIdx, unsigned int endIdx);
	~FFTBandPassProcessor();

	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);

private:
	std::vector<Complex> myBuf;
	unsigned int startIdx;
	unsigned int endIdx;
};

/*
FFTShiftUpProcessor moves all fft entries 'up'. So index 0 moves to index 0+amount and everything that is less than amount is nulled.
*/
class FFTShiftUpProcessor : public FFTProcessor
{
public:
	FFTShiftUpProcessor(unsigned int amount);
	~FFTShiftUpProcessor();
	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);

private:
	std::vector<Complex> myBuf;
	unsigned int amount;
};

/*
FFTShiftDownProcessor moves all fft entries 'down'. So index amount moves to index 0, everything between length/2 and (length/2)-amount is nulled.
*/
class FFTShiftDownProcessor : public FFTProcessor
{
public:
	FFTShiftDownProcessor(unsigned int amount);
	~FFTShiftDownProcessor();
	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);

private:
	std::vector<Complex> myBuf;
	unsigned int amount;
};

/*
FFTBinMaximizer raises the amplitude of the selected bins to the maximum amplitude in each input
*/
class FFTBinMaximizer : public FFTProcessor
{
public:
	FFTBinMaximizer(unsigned int* bins, unsigned int length);
	~FFTBinMaximizer();

	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);
	std::vector<unsigned int> bins;

private:
	std::vector<Complex> myBuf;
};

/*
FFTBinDampener diminishes all entries below a certain threshold
*/
class FFTBinDampener : public FFTProcessor
{
public:
	FFTBinDampener(unsigned int untouchedCount);
	~FFTBinDampener();

	Complex* Process(Complex* complex, unsigned int length, SortedAmplitudes* sortedAmplitudes);

private:
	std::vector<Complex> myBuf;
	unsigned int untouchedCount;
};